category: main
step: 5_full
sub_step:
doc_status: ready
language: rus
main_number: "08"
macro full
Name | Category | In Main Macro | Doc Status |
---|---|---|---|
clickhouse__check_table_exists | auxiliary | full | ready |
Макрос full
предназначен для объединения данных пайплайна с registry
-таблицами. В зависимости от пайплайна поведение макроса меняется.
Имя dbt-модели (=имя файла в формате sql в папке models) должно соответствовать шаблону:
full_{название_пайплайна}
.
Например, full_events
.
Внутри этого файла вызывается макрос:
{{ datacraft.full() }}
Над вызовом макроса в файле будет указана зависимость данных через —depends_on
. То есть целиком содержимое файла выглядит, например, вот так:
-- depends_on: {{ ref('graph_qid') }}
-- depends_on: {{ ref('link_events') }}
-- depends_on: {{ ref('link_registry_appprofilematching') }}
-- depends_on: {{ ref('link_registry_utmhashregistry') }}
{{ datacraft.full() }}
Этот макрос принимает следующие аргументы:
params
(по умолчанию: none)disable_incremental
(по умолчанию: none)override_target_model_name
(по умолчанию: none)date_from
(по умолчанию: none)date_to
(по умолчанию: none)limit0
(по умолчанию: none)metadata
(по умолчанию: результат макроса project_metadata()
)Глобально работу макроса можно разделить на несколько смысловых этапов:
registry
1) подготовка
Сначала макрос считает имя модели - либо из передаваемого аргумента (
override_target_model_name
), либо из имени файла (this.name
). При использовании аргумента override_target_model_name
макрос работает так, как если бы находился в модели с именем, равным значению override_target_model_name
.
Название модели, полученное тем или иным способом, разбивается на части по знаку нижнего подчёркивания. Например, название full_events
разобьётся на 2 части, из этих частей макрос возьмёт в работу:
pipeline_name
→ events2) материализация
Для каждого пайплайна в макросе задаётся своя материализация и своё поведение в начале, до присоединения registry-таблиц:
для пайплайна events
- материализация table
и соединение данных из таблиц link_events
+ graph_qid
+ имеющихся таблиц пайплайна registry
для пайплайна datestat
- материализация incremental
и соединение данных из таблицы link_datestat
+ имеющихся таблиц пайплайна registry
для пайплайна periodstat
- материализация incremental
и разбиение метрик по дням + добавление имеющихся таблиц пайплайна registry
3) отбор возможных и существующих таблиц пайплайна registry
На этом этапе сначала в макросе создаётся список возможных таблиц пайплайна registry
- это нужно для всех пайплайнов. Для создания такого списка макрос обращается к metadata
(она передаётся через аргумент и по умолчанию metadata
= результат макроса project_metadata()
).
Далее макрос при помощи вспомогательного макроса clickhouse__check_table_exists будет отбирать те таблицы пайплайна registry
, которые в действительности существуют.
4) создание основы запроса для каждого пайплайна
Для каждого пайплайна создаём основу будущего SQL-запроса. Оформляем это при помощи common table expressions
- CTE
. Этот основной CTE
для всех пайплайнов будет называться одинаково: t0
. К нему в дальнейшем будут добавляться t1
, t2
и т.д. в зависимости от количества имеющихся таблиц пайплайна registry
.
Для каждого пайплайна основа данных - своя. Поэтому макрос начинает перебор пайплайнов с помощью оператора if
и каждому задаёт свою основу основу будущего запроса - t0
:
events
основа запроса это link_events
+ graph_qid
:WITH t0 AS (
SELECT * FROM {{ ref('link_events') }}
LEFT JOIN {{ ref('graph_qid') }} USING (__id, __link, __datetime)
)
datestat
это link_datestat
:WITH t0 AS (
SELECT * FROM {{ ref('link_datestat') }}
)
periodstat
поведение макроса более насыщенное: на этом этапе макрос берёт данные из link_periodstat
и разбивает их по дням.Чтобы осуществить это поведение, внутри макроса понадобится произвести дополнительные действия.
В макросе задаются наименования числовых типов данных:
{%- set numeric_types = ['UInt8', 'UInt16', 'UInt32', 'UInt64', 'UInt256',
'Int8', 'Int16', 'Int32', 'Int64', 'Int128', 'Int256',
'Float8', 'Float16','Float32', 'Float64','Float128', 'Float256','Num'] -%}
Затем задаются два списка - для колонок с числовыми и нечисловыми типами данных.
Далее макрос при помощи вспомогательного макроса get_columns_in_relation
берёт все колонки, проверяет у каждой тип данных, и распределяет их по этим двум спискам.
Для этого пайплайна макрос не сразу создаёт t0
, а сначала делает подготовительный шаг - unnest_dates
.
Здесь макрос разбивает период на дни. Например, в данных была одна строка с такими значениями:
Из этой одной строки макрос создаст 31 строку - по одной на каждый день этого периода и значением в новом столбце cost_per_day
равным 1000. Вот как выглядит подготовительный шаг unnest_dates
:
WITH unnest_dates AS (
SELECT *, {# берём все данные, какие были в таблице и добавляем к ним каждый день периода #}
dateAdd(periodStart, arrayJoin(range( 0, 1 + toUInt16(date_diff('day', periodStart, periodEnd))))) AS period_date
, COUNT(*) OVER(PARTITION BY
{% for c in columns_not_numeric -%}{{c}}
{% if not loop.last %},{% endif %}
{% endfor %}
) AS divide_by_days {# здесь мы вычисляем кол-во дней, на которое надо будет в дальнейшем делить метрики #}
FROM {{ ref('link_periodstat') }}
)
и после этого подготовительного шага уже создаётся t0
. На этом шаге идёт отбор всех дат периода, нечисловые колонки идут в запрос SELECT в таком же виде, в каком они были изначально. А значения в числовых колонках делятся на количество дней в периоде. Таким образом для числовых данных образуются новые колонки, названия которых заканчиваются на _per_day
. Например, была числовая колонка cost
, а станет колонка cost_per_day
. Вот как выглядит код этого шага:
, t0 AS (
SELECT period_date, {# отбираем все даты периода #}
{% for column in columns_not_numeric -%}{{column}}, {# не числовые колонки - такими какими они и были #}
{% endfor %} {# а значения в числовых колонках делим на количество дней в периоде #}
{% for column in columns_numeric -%}{{column}}/divide_by_days AS {{column}}_per_day {# и таким образом получаем новые столбцы #}
{% if not loop.last %},{% endif %} {# например вместо cost будет cost_per_day #}
{% endfor %}
FROM unnest_dates
)
5) отбор полей pipeline_columns для каждого пайплайна
На этом шаге нет условия if, но, поскольку при вызове моделей у каждой свой pipeline_name, значения будут разными.
Макрос задаёт переменную pipeline_columns
, в которую будет отбирать колонки с сущностями каждого пайплайна.
Далее на этом шаге макрос обращается к metadata
, отбирает линки и получает все необходимые для дальнейшей работы данные по каждому нужному линку.
Ранее созданная переменная pipeline_columns
заполняется теми сущностями (entities
) из метадаты, которые соответствуют пайплайну модели и её линкам. Каждое название сущности дополняется окончанием Hash
, и таким образом в этой переменной в процессе работы макроса оказывается уникальный список захэшированных названий колонок сущностей.
Например, для пайплайна events
такой список (pipeline_columns
) может выглядеть следующим образом:
'AccountHash', 'AppMetricaDeviceHash', 'MobileAdsIdHash', 'CrmUserHash', 'OsNameHash', 'CityHash', 'AdSourceHash', 'UtmParamsHash', 'UtmHashHash', 'TransactionHash', 'PromoCodeHash', 'AppSessionHash', 'VisitHash', 'YmClientHash'
6) последовательное обогащение основного запроса данными из таблиц пайплайна registry
На этом шаге в макросе происходит цикл for
для последовательных джойнов: основа запроса - t0
- последовательно обогащается данными из таблиц пайплайна registry
(автоматически создаются и добавляются t1
, t2
).
Чтобы джойны могли отработать, в макросе создаётся переменная fields_list
. В неё будут отбираться поля для будущего USING(...)
в блоке JOIN
.
Макрос перебирает таблицы в ранее отобранном списке существующих таблиц пайплайна registry
(см. шаг 3). В названии этих registry
-таблиц последняя часть названия - это линк.
Макрос обращается на этом этапе к metadata
. И для каждого линка из названия registry
-таблиц макрос отбирает нужную для дальнейшей работы информацию об этом линке из metadata
.
Далее в переменную fields_list
добавляются названия отобранных для задействованных линков сущностей (entities
) . К названиям сущностей добавляются окончания Hash
(так же, как это происходило для наполнения переменной pipeline_columns
).
После этого макрос создаёт переменную existing_fields_list
. В неё отбираются только те значения из fields_list
, которые есть в pipeline_columns
.
Таким образом в existing_fields_list
попадают те названия полей, которые не просто относятся к пайплайну (то есть являются возможными вариантами для пайплайна), но и есть в реально существующих таблицах.
Далее макрос проходит циклом:
t1
, который обогащает t0
t2
, который обогащает t1
и тд.Каждая таблица из t1
, t2
и тд - это одна из таблиц пайплайна registry
.
Для каждого оборота цикла в макросе автоматически подставляются и таблица registry
, и её хэш-поля в USING(...)
для верного джойна.
Джойн происходит до тех пор, пока у нас есть общие поля, по которым можно сделать USING(...)
.
Если общих полей для USING(...)
нет, то мы этот шаг делаем без джойна, просто как SELECT * FROM
предыдущий шаг.
После завершения цикла макрос обратится к последнему CTE и возьмёт все колонки кроме тех, которые не пойдут по условию с регулярным выражением. Регулярное выражение отсеет технически ненужные для дальнейшей работы колонки.
Если аргумент limit0
активирован, то в конце SQL-запроса будет добавлено LIMIT 0
.
Файл в формате sql в папке models. Название файла full_events
Содержимое файла:
-- depends_on: {{ ref('graph_qid') }}
-- depends_on: {{ ref('link_events') }}
-- depends_on: {{ ref('link_registry_appprofilematching') }}
-- depends_on: {{ ref('link_registry_utmhashregistry') }}
{{ datacraft.full() }}
Это восьмой из основных макросов.